/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package yuhuibear.poat.object.bean;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import yuhuibear.poat.annotations.action.Macro;
import yuhuibear.poat.annotations.relation.Table;
import yuhuibear.poat.annotations.relation.View;
import yuhuibear.poat.exception.MissAttributesOfBean;
import yuhuibear.poat.exception.NotMappedType;
import yuhuibear.poat.macro.MacroDefine;
import yuhuibear.poat.util.NameConvertor;
import yuhuibear.poat.util.Tokens;

/**
 * 数据库创建类<br>
 * 用于完成持久化实体类的加载与解析。
 * 
 * @author yuhuibear
 */
public class PersistentClassLoader {

	private static Log log = LogFactory.getLog(PersistentClassLoader.class);

	private final int ClassSuffixLength = ".class".length();

	private String packageName;

	private String fs_packageName;

	/**
	 * (实体类类型， 实体的描述类)
	 */
	private Map<Class<?>, BeanStruct> structs;

	/**
	 * 被映射到数据库的实体类
	 */
	private Set<Class<?>> mappedEntity;

	/**
	 * 实体类的短命名， 实体类的长命名
	 */
	private Map<String, Class<?>> shortBeanAndEntity;

	private PersistentClassLoader() {
		structs = Collections.synchronizedMap(new HashMap<Class<?>, BeanStruct>());
		mappedEntity = Collections.synchronizedSet(new HashSet<Class<?>>());
		shortBeanAndEntity = Collections.synchronizedMap(new HashMap<String, Class<?>>());
	}

	private static PersistentClassLoader self;

	public static PersistentClassLoader getInstance() {
		if (self == null)
			self = new PersistentClassLoader();
		return self;
	}

	public String convertAttributeExpression(String atrb) throws Exception {
		if (atrb.length() < 1 || atrb == null)
			return null;

		int ie = atrb.indexOf("=");
		if (ie > 0) {
			String atr = atrb.substring(0, ie);
			String value = atrb.substring(ie);
			atr = NameConvertor.toTableName(atr.replaceFirst("\\w*\\.", ""));
			value = convertExpressionToDBForm(value);
			return (atr + value).trim();
		}
		else return NameConvertor.toTableAttributes(atrb).trim();
	}

	/**
	 * 按照映射名称来转sql
	 * 
	 * @param exp
	 * @return
	 * @throws Exception
	 */
	public String convertExpressionToDBForm(String exp) throws Exception {
		StringBuffer cv_exp = new StringBuffer();
		boolean notInQuote = true;
		char prvChar = ' ';
		int cp_st = 0;
		for (int i = 0; i < exp.length(); i++) {
			char ch = exp.charAt(i);
			if (ch == '\'') {
				if (prvChar == '\\') {
					cv_exp.append(exp.substring(cp_st, i - 1));
					cp_st = i;
				}
				else {
					notInQuote = !notInQuote;
				}
			}
			if (notInQuote && Character.isUpperCase(ch)) {
				int start = i;
				int dot = -1;
				do {
					ch = exp.charAt(i);
					if (ch == '.') {
						dot = i;
					}
					if (Tokens.contains(ch)) {
						break;
					}
				} while (i++ < exp.length() - 1);
				if (dot > 0 && start + 1 < exp.length()) {
					// extract attribute
					String attrOfBean = exp.substring(start, i);
					i--;
					// convert to db form.
					String beanName = attrOfBean.substring(0, attrOfBean.indexOf('.'));
					Class<?> entityType = this.getEntityClass(beanName);
					if (entityType == null) { throw new NotMappedType(beanName); }
					BeanStruct bstruct = this.analyse(entityType);
					String dbAttrOfBean = bstruct.getAttributeMappedFullNameByName(attrOfBean);
					if (dbAttrOfBean == null) { throw new MissAttributesOfBean(entityType, attrOfBean); }
					cv_exp.append(exp.substring(cp_st, start));
					cv_exp.append(dbAttrOfBean);
					cp_st = i + 1;
				}
			}
			prvChar = ch;
		}
		if (cp_st < exp.length()) {
			cv_exp.append(exp.substring(cp_st, exp.length()));
		}

		return cv_exp.toString().trim();
	}

	/**
	 * 判断指定的类是否是数据库的实体类。
	 * 
	 * @param type
	 * @return
	 */
	public boolean isDBEntity(Class<?> type) {
		return mappedEntity.contains(type);
	}

	/**
	 * 用一指定的短命名来获取对应的JAVA 类。
	 * 
	 * @param name
	 *            , 已经被映射到数据库的JAVA类的短命名。
	 * @return
	 */
	public Class<?> getEntityClass(String beanName) {
		return shortBeanAndEntity.get(beanName);
	}

	/**
	 * 扫描一个指定的包及其子包， 并分析所有的信息。
	 * 
	 * @param packageName
	 * @return 所有被描述为BeanDescriptor的类的个数。
	 * @throws java.lang.Exception
	 */
	public int scanPackage(String packageName) throws Exception {
		log.info(String.format("location: %s, scanning... ", packageName));
		this.packageName = packageName;
		fs_packageName = packageName.replace('.', '/');
		int cnt = 0;
		for (Enumeration<URL> enus = this.getClass().getClassLoader().getResources(fs_packageName); enus.hasMoreElements();) {
			URL url = enus.nextElement();
			if (url.getProtocol().equals("jar")) {
				JarURLConnection con = (JarURLConnection) url.openConnection();
				JarFile jar = con.getJarFile();
				cnt = cnt + explore(structs, jar);
			}
			else {
				File root = new File(new URI(url.toExternalForm()));
				cnt = cnt + explore(structs, root);
			}
		}
		for (Map.Entry<Class<?>, BeanStruct> entry : structs.entrySet()) {
			BeanStruct struct = entry.getValue();
			if (struct.hasAnnotation(Table.class)
					|| struct.hasAnnotation(View.class)) {
				mappedEntity.add(entry.getKey());
				shortBeanAndEntity.put(entry.getValue().getShortName(), entry.getValue().getType());
			}
		}
		log.info(String.format("location: %s, loaded %d persistent class.", packageName, cnt));
		return cnt;
	}

	private int explore(Map<Class<?>, BeanStruct> beans, JarFile jar)
			throws Exception {
		int cnt = beans.size();
		for (Enumeration<JarEntry> je = jar.entries(); je.hasMoreElements();) {
			JarEntry entry = je.nextElement();
			String className = entry.getName();
			if (!className.startsWith(fs_packageName)) {
				continue;
			}
			if (!className.endsWith(".class")) {
				continue;
			}
			className = className.substring(0, className.length()
					- ClassSuffixLength).replace('/', '.');
			Class<?> type = null;
			try {
				type = Class.forName(className);
			} catch (java.lang.NoClassDefFoundError ncdf) {
				log.error("cannot found class : " + className);
				continue;
			}
			addToMap(beans, type);
		}
		return beans.size() - cnt;
	}

	private int explore(Map<Class<?>, BeanStruct> beans, File rootDirectory)
			throws Exception {
		int rootLength = rootDirectory.getAbsolutePath().length();
		int cnt = beans.size();
		Stack<File> stack = new Stack<File>();
		stack.push(rootDirectory);
		while (!stack.isEmpty()) {
			File curFile = stack.pop();
			File[] fs = curFile.listFiles(classFilter);
			for (File f : fs) {
				if (f.isDirectory()) {
					stack.push(f);
				}
				else {
					String className = f.getAbsolutePath();
					className = className.substring(rootLength).replace(File.separatorChar, '.');
					if (!className.startsWith(".")) {
						className = "." + className;
					}
					className = this.packageName + className;
					className = className.substring(0, className.length()
							- ClassSuffixLength);
					Class<?> type = null;
					try {
						type = Class.forName(className);
					} catch (java.lang.NoClassDefFoundError ncdf) {
						log.error("cannot found class : " + className);
						continue;
					}
					addToMap(beans, type);
				}
			}
		}
		return beans.size() - cnt;
	}

	private boolean addToMap(Map<Class<?>, BeanStruct> beans, Class<?> type)
			throws Exception {
		if (type == null || type.isInterface() || type.isEnum()
				|| type.isAnonymousClass() || type.isAnnotation()) {
			log.debug("[" + beans.size() + "] skip: " + type);
			return false;
		}
		BeanStruct beanDscp = analyzBean(type);
		beans.put(type, beanDscp);
		log.debug("[" + beans.size() + "] loaded : " + type);
		return true;
	}

	/**
	 * 解析一个类型的Bean
	 * 
	 * @param type
	 * @return
	 * @throws java.lang.Exception
	 */
	public BeanStruct analyse(Class<?> type) throws Exception {
		BeanStruct bdscrp = this.structs.get(type);
		if (bdscrp == null) {
			bdscrp = analyzBean(type);
			Table tableNot = bdscrp.getAnnotation(Table.class);
			if (tableNot != null) {
				mappedEntity.add(bdscrp.getType());
				shortBeanAndEntity.put(bdscrp.getShortName(), bdscrp.getType());
			}
			this.structs.put(type, bdscrp);
		}
		return bdscrp;
	}

	private BeanStruct analyzBean(Class<?> type) throws Exception {
		BeanStruct bDscrp = new BeanStruct(type);
		// deal annotations of class.
		bDscrp.addAnnotations(type.getAnnotations());
		// deal annotations of all fields and them getters and setters.
		Map<String, AttributeMapper> attributes = new HashMap<String, AttributeMapper>();
		PropertyDescriptor[] pds = Introspector.getBeanInfo(type).getPropertyDescriptors();
		for (PropertyDescriptor cpd : pds) {
			if (cpd.getName().equals("class")
					|| cpd.getName().equals("declaringClass")) {
				continue;
			}
			AttributeMapper amapper = new AttributeMapper();
			amapper.setName(cpd.getName());
			amapper.setAttributeType(cpd.getPropertyType());
			Method mthd = cpd.getReadMethod();
			if (mthd != null) {
				amapper.setGetter(mthd);
				amapper.addAnnotation(mthd.getAnnotations());
				bDscrp.addAnnotations(mthd.getAnnotations());
			}
			else {
				throw new Exception(type.getName() + " has no getter for "
						+ cpd.getName());
			}
			mthd = cpd.getWriteMethod();
			if (mthd != null) {
				amapper.setSetter(mthd);
				amapper.addAnnotation(mthd.getAnnotations());
				bDscrp.addAnnotations(mthd.getAnnotations());
			}
			else {
				throw new Exception(type.getName() + " has no setter for "
						+ cpd.getName());
			}
			Field field;
			try {
				field = type.getDeclaredField(cpd.getName());
			} catch (NoSuchFieldException e) {
				log.error("no field: " + type.getName() + "." + cpd.getName(), e);
				throw e;
			}
			if (field != null) {
				amapper.addAnnotation(field.getAnnotations());
				bDscrp.addAnnotations(field.getAnnotations());
			}
			attributes.put(amapper.getDBName(), amapper);
		}
		bDscrp.setAttributes(attributes);
		// deal macros.
		for (Method md : type.getDeclaredMethods()) {
			Macro mac = md.getAnnotation(Macro.class);
			if (mac == null)
				continue;
			if (!(md.getReturnType().equals(String.class) && md.getParameterTypes().length == 0))
				continue;
			Object obj = type.newInstance();
			bDscrp.addMacro(new MacroDefine(type, md.toString(), mac.name(), mac.depend(), (String) (md.invoke(obj))));
		}
		return bDscrp;
	}

	/**
	 * 获取所有的已经被扫描到的bean 信息。
	 * 
	 * @return
	 */
	public Map<Class<?>, BeanStruct> getAllStructs() {
		return structs;
	}

	public String getPackageName() {
		return packageName;
	}

	private final FileFilter classFilter = new FileFilter() {

		@Override
		public boolean accept(File pathname) {
			if (pathname.getName().endsWith(".class") || pathname.isDirectory()) {
				return true;
			}
			else {
				return false;
			}
		}
	};
}
